package com.echo.im.domain.message.service;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.echo.im.common.vo.GroupMessageVO;

import com.echo.im.domain.message.dto.GroupMessageACKDTO;
import com.echo.im.domain.group.dto.PullGroupOfflineMessageDTO;
import com.echo.im.domain.group.manager.GroupMemberManager;
import com.echo.im.domain.message.manager.GroupMessageManager;
import com.echo.im.infrastructure.session.SessionContext;
import com.echo.im.infrastructure.session.UserSession;
import com.echo.im.repository.dao.entity.GroupMessage;
import com.echo.im.repository.dao.mapper.GroupMessageMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * @author: echo
 * @time: 2024-08-15
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, GroupMessage> implements GroupMessageService {

    private final GroupMemberManager groupMemberManager;
    private final GroupMessageManager groupMessageManager;


    /**
     * 群聊消息的拉取离线消息和其他的不同，群聊的离线消息只能拉取最近三天的消息，群聊消息的记录表里面记录了用户的ack
     * 用户在前端建议十秒进行一次ack，如果用户没有ack，im-server会直接申请断开连接，然后用户刷新的时候自己来ap取消息
     */
    @Override
    public List<GroupMessageVO> pullOfflineMessage(PullGroupOfflineMessageDTO dto) {

        //1. 此次拉取是对上次拉取的ack，所以可以将last_ack_message_id更新,更新为当前的lastAckMessageId
        if (!Objects.isNull(dto.getLastAckMessageId())){
            groupMemberManager.updateLastAckMessageId(dto.getGroupId(),SessionContext.getSession().getUserId(), dto.getLastAckMessageId());
        }

        //2. 根据该lastAckMessageId去增量拉取群消息
        List<GroupMessage> offlineMessageList = baseMapper.selectListByGroupIdAndLastAckMessageIdLimitBySize(dto.getGroupId(), dto.getLastAckMessageId(), dto.getSize());

        //3.然后返回消息列表，但是并不更新lastAckMessageId，要等待下一次拉取的时候再刷新
        List<GroupMessageVO> groupMessageVOList = BeanUtil.copyToList(offlineMessageList, GroupMessageVO.class);

        return groupMessageVOList;
    }


    /**
     * 群聊消息不能对每条消息进行ack，所以其无法避免客户端应用层处理消息失败的情况，如果是这冲情况，唯有客户端主动申请重新拉取离线消息
     * 把逻辑放到客户端去实现
     * @param dto
     */
    @Override
    public void ackMessage(GroupMessageACKDTO dto) {
        UserSession session = SessionContext.getSession();
        groupMemberManager.updateLastAckMessageId(dto.getGroupId(),session.getUserId(), dto.getLastAckMessageId());
    }

    /**
     * 此功能是为回执消息服务的，也就是群聊消息类型中的回执消息，发送者想要查看此消息的阅读人的信息
     * 其实这里可以改换为redis的策略，因为此消息实时性很强，如果放到redis，一般设置有效期为3天，时间一过就直接不管了
     * 如果放到redis还要新增一个接口，当用户读到了回执消息的时候，请求更新redis中的set，
     * 所以 K：群消息id，V：set<UserId>
     * @param groupId
     * @param messageId
     * @return
     */
    @Override
    public List<Long> findReadedUsers(Long groupId, Long messageId) {
        UserSession session = SessionContext.getSession();

        //验证消息是否存在
        GroupMessage message = groupMessageManager.getAndCheckByMsgId(messageId);

        // 是否在群聊里面
        groupMemberManager.findAndCheckMemberByGroupAndUserId(groupId, session.getUserId());

        // 返回已读用户的id集合
        return groupMemberManager.getReadedUserIds(groupId, message.getSendId(),message.getId());
    }

    @Override
    public List<GroupMessageVO> findHistoryMessage(Long groupId, Long lastMsgId, Long size) {

        List<GroupMessage> groupMessageList = baseMapper.selectListByGroupIdAndIdLimitBySize(groupId, lastMsgId, size);
        if (CollectionUtil.isNotEmpty(groupMessageList)) {
            return BeanUtil.copyToList(groupMessageList, GroupMessageVO.class);
        }

        return Collections.emptyList();
    }
}
